<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app"></div>

    <script crossorigin src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
    <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

    <script type="text/babel">
        // useState 简单实现
        const values = []; // 存储当前state值
        const setters = []; // 存储 state 更新的方法
        let stateIndex = 0; // state 索引
        function useState(initialState) {
            // 首次渲染，初始化值和更新方法。更新时，useState 会再次调用，不做改变
            values[stateIndex] = values[stateIndex] ? values[stateIndex] : initialState;
            setters[stateIndex] = setters[stateIndex] ? setters[stateIndex] : createSetter(stateIndex);
            // values[stateIndex] ?? (values[stateIndex] = initialState)
            // setters[stateIndex] ?? (setters[stateIndex] = createSetter(stateIndex)) 
            const value = values[stateIndex];
            const setter = setters[stateIndex];
            stateIndex++;
            return [value, setter];
        }

        function createSetter(index) {
            return function (newState) {
                values[index] = newState;
                render();
            }
        }

        function render() {
            // 重置索引；执行effect的cleanup操作；重新渲染组件
            stateIndex = 0;
            effectIndex = 0;
            cleanupCbs.forEach(cb => cb && cb());
            ReactDOM.render(<Demo />, document.getElementById('app'));
        }

        // useEffect 简单实现
        // 1. 第一个参数必须是函数
        // 2. 第二个参数不传 或者是 undefined，每次更新都会执行回调
        // 3. 第二个参数是空数组，仅首次渲染执行一次回调，（清理函数不会执行）
        // 4. 第二个参数依赖的数组项变化时，执行回调，卸载时执行清理工作。
        const prevDepsArr = [];
        const cleanupCbs = [];
        let effectIndex = 0;
        function useEffect(cb, deps) {
            if(typeof cb !== 'function') throw new Error('useEffect 第一个参数必须是函数');
            // 没有传入 deps，直接执行一次回调即可
            if(typeof deps === 'undefined') {
                cb();
                return;
            }
            // 实际 react 中，如果第二个参数不是数组，如null, false, 1, 'string' 等，行为与 [] 类似
            if(Object.prototype.toString.call(deps) !== '[object Array]') {
                throw new Error('useEffect 第二个参数必须是数组');
            }
            const preDeps = prevDepsArr[effectIndex];
            const hasChanged = preDeps ? deps.every((dep,index) => dep === preDeps[index]) === false : true;
            if(hasChanged) {
                const cleanup = cb();
                // 如果是空数组，cleanup 不会执行
                typeof deps.length && cleanup === 'function' && (cleanupCbs[effectIndex] = cleanup);
            };
            prevDepsArr[effectIndex] = deps;
            effectIndex++;
        }

        // useReducer实现
        function useReducer(reducer, initialState) {
            const [state, setState] = useState(initialState);
            const dispatch = function(action) {
                const newState = reducer(state, action);
                setState(newState);
            }
            return [state, dispatch];
        }
    </script>

    <script type="text/babel">
        // const { useState } = React;
        // const { useEffect } = React;
        // const { useReducer } = React;

        function Demo() {
            const [random, setRandom] = useState(0);
            const [name, setName] = useState('chuanhua');

            useEffect(() => {
                console.log('useEffect');
                return () => {
                    console.log('unmounted')
                };
            }, []);

            useEffect(() => {
                console.log('useEffect random name');
                return () => {
                    console.log('unmounted random name')
                };
            }, [random, name]);

            const reducer = function(state, action) {
                switch(action.type) {
                    case 'increment':
                        return state + 1;
                    case 'decrement':
                        return state - 1;
                    default:
                        return state;
                }
            };
            const [count, dispatchCount] = useReducer(reducer, 0)

            return (
                <div>
                    <button onClick={() => setRandom(Math.random())}>生成随机数</button>
                    {random}
                    <br />
                    <input value={name} onChange={e => setName(e.target.value)} />
                    {name}
                    <br />
                    <button onClick={() => dispatchCount({type: 'decrement'})}>-</button>
                    {count}
                    <button onClick={() => dispatchCount({type: 'increment'})}>+</button>
                </div>
            )
        }

        ReactDOM.render(<Demo />, document.getElementById('app'));
    </script>
</body>

</html>